今天不寫 promise,換個口味寫寫 JS 的陣列方法 forEach
。
之前在面試的時候被問到這個問題:可以中斷 forEach
嗎?當時不太肯定,但心裡想的是,既然這個方法是語意化的做迴圈的事情,那總有類似迴圈中斷的功能吧!?
後來才知道,答案是:不行。或者說硬要也是有辦法,但不行是比較好的結論。
說到中斷迴圈首先想到 break
,但在這裡並不適用。這部分還好理解,因為 break
是用來中斷迴圈或 switch
,中斷後 control 會去到後面的陳述式。
既然不能使用 break
,那如果查查看 forEach
本身有沒有中斷的方式呢?就會看到 MDN 上這樣寫:「中斷 forEach
的唯一辦法是丟出錯誤,但如果有需要這樣做,就代表 forEach
是錯誤的選擇。」非常直接的告訴你,要中斷請換方法。
雖然如此,換方法前還是可以看一下在 forEach
中的一些行為:
const array = [1, 2, 3, 4]
array.forEach((element) => {
if (element === 2) {
return;
}
console.log(element);
});
// 1, 3, 4
這樣會從 forEach
的 callback 中 return,可以有像迴圈中使用 continue
的效果。
const array = [1, 2, 3, 4]
try {
array.forEach((element) => {
if (element === 2) {
throw new Error("Break the loop.")
}
console.log(element);
})
} catch (error) {
console.error(error);
}
// 印出 1 之後會報錯
如上面所寫,是可以讓 forEach
停下來沒錯,但用錯誤處理來作為程式邏輯不是理想的方法。
假設有中斷的需求,想改用別的方法,第一種選項是樸實無華的 for
迴圈 (或是 for...in
、for...of
等其他迴圈方式),雖然不像陣列方法簡潔但很可靠。另外當碰到一些限制 (例如 forEach
不接受非同步函式),也可以考慮使用迴圈。
再來也可以依需求情境選擇其他陣列方法:
find()
和 findIndex()
這兩個方法都會對陣列的每個元素執行 callback,並回傳第一個符合的元素/ 元素索引值,並且有符合的值就會中斷對剩下元素的操作。如果都不符合, find()
會回傳 undefined
,findIndex()
會回傳 -1。
// 12
[5, 12, 8, 130, 44].find(element => element > 10);
// 2
[1, 2, 3].findIndex(number => number === 3)
some()
和 every()
可以用來檢查陣列中是否有特定目標,兩者都會對陣列的每個元素執行 callback,並回傳布林值,差別在於:
some()
在任一個元素回傳 true 時就會回傳 true 並中斷,都不符合則回傳 false。也就是代表至少有一個符合的元素。every()
則是所有元素都符合才回傳 true,只要任一個元素為 false 就回傳 false。也就是適用需要全部都符合的情境。// true
[1, 2, 3].some(number => number < 2)
// false
[1, 2, 3].every(number => number < 2)
看過有教學寫說可以利用 「some()
回傳 true 會中斷、every()
回傳 false 會中斷」的特性,用回傳布林來終止陣列迭代。運作上沒錯,但個人覺得這樣是本末倒置,而且浪費方法本身的語意、不見得好讀,還不如使用 for
迴圈。
補充找資料過程中看到一篇有趣的文章。文章的前提有點驚人:作者有次當面試官,碰到一個無法回答 while
迴圈問題的中階應徵者,而對方的理由是他只寫宣告式 (declarative) 程式碼,對於指令式 (imperative) 程式不屑一顧。以此為出發點,作者做了一些實驗來比較 map
, reduce
, forEach
這些方法和 for
迴圈的效能表現。雖然效能並非唯一指標,作者也很中肯的說在多數專案這些方法差異不大,但文章中有些不錯的建議 (例如很多時候不妨以 for..of
取代 forEach
,兼具效能及可讀性),然後延伸到宣告式與指令式程式的比較,可供額外思考。
其他參考資料:
https://webtips.dev/break-from-foreach-in-javascript
https://www.tutorialspoint.com/how-to-stop-foreach-method-in-javascript
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
https://www.freecodecamp.org/news/javascript-loops-label-statement-continue-statement-and-break-statement-explained/